package com.gitee.zydaas.gateway.plugin.auth;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;

import com.alibaba.fastjson2.JSON;
import com.gitee.zydaas.common.core.constant.GlobalConstants;

import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 网关自定义鉴权管理器
 *
 * @author kinbug
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {

	private final RedisTemplate<String, Object> redisTemplate;

	@Override
	public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {

		log.debug("ResourceServerManager：文件服务器资源管理器:检查");

		ServerHttpRequest request = authorizationContext.getExchange().getRequest();
		if (request.getMethod() == HttpMethod.OPTIONS) { // 预检请求放行
			return Mono.just(new AuthorizationDecision(true));
		}
		PathMatcher pathMatcher = new AntPathMatcher();
		String method = request.getMethodValue();
		String path = request.getURI().getPath();
		String restfulPath = method + ":" + path; // RESTFul接口权限设计: https://www.cnblogs.com/haoxianrui/p/14961707.html

		// 如果token以"bearer "为前缀，到此方法里说明JWT有效即已认证
		String token = request.getHeaders().getFirst("Authorization");
		if (StringUtils.isNotBlank(token) && StringUtils.containsIgnoreCase(token, "Bearer ")) {
			if (path.contains("/app-api")) {
				// 商城移动端请求需认证不需鉴权放行（根据实际场景需求）
				return Mono.just(new AuthorizationDecision(true));
			}
		} else {
			return Mono.just(new AuthorizationDecision(false));
		}

		/**
		 * 鉴权开始
		 *
		 * 缓存取 [URL权限-角色集合] 规则数据 urlPermRolesRules =
		 * [{'key':'GET:/api/v1/users/*','value':['ADMIN','TEST']},...]
		 */
		Map<Object, Object> urlPermRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);

		// 根据请求路径获取有访问权限的角色列表
		List<String> authorizedRoles = new ArrayList<>(); // 拥有访问权限的角色
		boolean requireCheck = false; // 是否需要鉴权，默认未设置拦截规则不需鉴权

		for (Entry<Object, Object> permRoles : urlPermRolesRules.entrySet()) {
			String perm = permRoles.getKey().toString();
			if (pathMatcher.match(perm, restfulPath)) {
				List<String> roles = JSON.parseArray(permRoles.getValue().toString(), String.class);
				authorizedRoles.addAll(roles);
				if (!requireCheck) {
					requireCheck = true;
				}
			}
		}
		// 没有设置拦截规则放行
		if (!requireCheck) {
			return Mono.just(new AuthorizationDecision(true));
		}

		// 判断JWT中携带的用户角色是否有权限访问
		Mono<AuthorizationDecision> authorizationDecisionMono = mono.filter(Authentication::isAuthenticated)
				.flatMapIterable(Authentication::getAuthorities).map(GrantedAuthority::getAuthority).any(authority -> {
					String roleCode = RegExUtils.removeFirst(authority, "ROLE_");// ROLE_ADMIN移除前缀ROLE_得到用户的角色编码ADMIN
					if (GlobalConstants.ROOT_ROLE_CODE.equals(roleCode)) {
						return true; // 如果是超级管理员则放行
					}
					boolean hasAuthorized = !CollectionUtils.isEmpty(authorizedRoles)
							&& authorizedRoles.contains(roleCode);
					return hasAuthorized;
				}).map(AuthorizationDecision::new).defaultIfEmpty(new AuthorizationDecision(false));
		return authorizationDecisionMono;
	}
}
